Skip to content

As a Toolkit Capability

Expose a multi-agent workflow as a tool inside a parent agent using the [MultiAgent] attribute. The parent agent can invoke the whole workflow like any other tool call.

Basic Usage

csharp
public class ResearchToolkit
{
    [MultiAgent("Run a full research and writing pipeline")]
    public async Task<AgentWorkflowInstance> ResearchPipeline()
    {
        return await AgentWorkflow.Create()
            .AddAgent("researcher", new AgentConfig
            {
                SystemInstructions = "Research the topic thoroughly."
            })
            .AddAgent("writer", new AgentConfig
            {
                SystemInstructions = "Write a clear answer based on the research."
            })
            .From("researcher").To("writer")
            .BuildAsync();
    }
}

var agent = await new AgentBuilder()
    .WithToolkit<ResearchToolkit>()
    .BuildAsync();

The parent agent sees ResearchPipeline as a callable tool. When it invokes it, the full workflow runs and all its events — workflow lifecycle events and each node's agent events — bubble up into the parent's RunAsync stream. There is no separate stream to consume.


Provider Inheritance

Agents inside the workflow that don't have their own Provider configured automatically inherit the parent agent's chat client at execution time — no extra configuration needed.

csharp
// These agents will use whatever model the parent agent uses
.AddAgent("researcher", new AgentConfig
{
    SystemInstructions = "..."
    // No Provider set — inherits from parent
})

[MultiAgent] Attribute

PropertyTypeDefaultDescription
Descriptionstring?Method nameDescription shown to the LLM.
Namestring?Method nameCustom tool name.
StreamEventsbooltrueStream inner workflow events to the parent's event stream.
TimeoutSecondsint300Workflow execution timeout in seconds.
csharp
[MultiAgent(
    "Analyze documents and produce a structured report",
    Name = "DocumentAnalysis",
    TimeoutSeconds = 600
)]
public async Task<AgentWorkflowInstance> AnalysisPipeline() { ... }

Typed metadata variant

For compile-time validation with conditional registration:

csharp
public class WorkflowMetadata : IToolMetadata
{
    public bool HasResearchCapability { get; set; }
}

public class PipelineToolkit
{
    [MultiAgent<WorkflowMetadata>("Full research pipeline")]
    [ConditionalFunction("HasResearchCapability")]
    public async Task<AgentWorkflowInstance> ResearchPipeline() { ... }
}

var agent = await new AgentBuilder()
    .WithToolkit<PipelineToolkit>(new WorkflowMetadata { HasResearchCapability = true })
    .BuildAsync();

→ See 02.1.4 Tool Dynamic Metadata.md for conditional registration details.


Events

When the parent agent invokes a [MultiAgent] tool, everything the workflow emits flows into the same RunAsync loop you're already consuming — no second stream, no separate subscription.

This means a single await foreach handles the parent agent's own events and the workflow's events interleaved:

csharp
await foreach (var evt in agent.RunAsync("Research quantum computing", sessionId: sessionId))
{
    switch (evt)
    {
        // Parent agent streaming its own response
        case TextDeltaEvent delta when delta.ExecutionContext?.Depth == 0:
            Console.Write(delta.Text);
            break;

        // Workflow lifecycle — emitted while the tool is running
        case WorkflowNodeStartedEvent node:
            Console.WriteLine($"\n[{node.NodeId} started]");
            break;

        case WorkflowNodeCompletedEvent node:
            Console.WriteLine($"[{node.NodeId} done — {node.Duration.TotalSeconds:F1}s]");
            break;

        // Text from a specific workflow node
        case TextDeltaEvent delta when delta.ExecutionContext?.AgentName == "writer":
            Console.Write(delta.Text);
            break;

        case MessageTurnFinishedEvent:
            Console.WriteLine();
            break;
    }
}

Distinguishing parent vs workflow events

Every AgentEvent carries ExecutionContext with two properties useful for filtering:

  • AgentName — the name of the specific agent that emitted it ("researcher", "writer", etc.)
  • Depth — nesting level. 0 = the parent agent itself, 1 = a direct workflow node, 2 = a SubAgent inside a node, and so on
  • AgentChain — the full name hierarchy from root to the emitting agent, e.g. ["ParentAgent", "ResearchPipeline", "writer"]

Workflow lifecycle events (WorkflowStartedEvent, WorkflowNodeStartedEvent, etc.) do not have ExecutionContext — they come from the graph orchestrator, not an agent. Pattern-match on them directly without checking ExecutionContext.

csharp
await foreach (var evt in agent.RunAsync(input, sessionId: sessionId))
{
    if (evt is AgentEvent agentEvt)
    {
        var depth = agentEvt.ExecutionContext?.Depth ?? 0;
        var name  = agentEvt.ExecutionContext?.AgentName ?? "unknown";

        // Only show text — indented by nesting level
        if (agentEvt is TextDeltaEvent delta)
        {
            var indent = new string(' ', depth * 2);
            Console.Write($"{indent}{delta.Text}");
        }
    }

    // Workflow events have no ExecutionContext — match directly
    if (evt is WorkflowCompletedEvent wf)
        Console.WriteLine($"\nWorkflow done — {wf.Duration.TotalSeconds:F1}s");
}

Disabling workflow event bubbling

Set StreamEvents = false on the attribute to suppress all workflow events from the parent stream — only the final tool result will be visible:

csharp
[MultiAgent("Run a full research and writing pipeline", StreamEvents = false)]
public async Task<AgentWorkflowInstance> ResearchPipeline() { ... }

→ For the complete workflow event reference, see 06.6 Workflow Events.

Released under the MIT License.